﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
using System.Diagnostics;

namespace ConsoleAppDemo {
    /// <summary>
    /// 多线程下载
    /// </summary>
    public class MultiDownLoad {
        /// <summary>
        /// 线程完成数量
        /// </summary>
        private int completeNum;
        /// <summary>
        /// 线程数量
        /// </summary>
        private const int threadNum = 5;
        /// <summary>
        /// 下载生成临时文件数组
        /// </summary>
        private List<string> tempFiles;
        private Thread[] threads;
        private string fileName;
        private string saveFileName;
        private long filesize;
        private int theLock = 0;
        private object _lock = new object();
        private Stopwatch watch;
        public MultiDownLoad(string fileName, string saveFileName)
        {
            this.fileName = fileName;
            this.saveFileName = saveFileName;
            tempFiles = new List<string>();
            threads = new Thread[threadNum];
            watch = new Stopwatch();
        }
        /// <summary>
        /// 开始运行
        /// </summary>
        public void Start()
        {
            /*
                步骤：
                    1.获取文件总大小，按指定的线程数分配文件大小给各个线程下载，如超出部分则直接给最后一个线程下载。
            */
            Stream stream = File.OpenRead(fileName);
            filesize = stream.Length;
            int singeNum = (int)(filesize / threadNum);//平均分配
            int remainder = (int)(filesize % threadNum);//剩余部分
            for (int i = 0; i < threadNum; i++)
            {
                List<int> ranges = new List<int>();
                ranges.Add(i * singeNum);
                if (remainder != 0 && (threadNum - 1) == i)
                    ranges.Add(i * singeNum + singeNum + remainder - 1);
                else
                    ranges.Add(i * singeNum + singeNum - 1);
                int[] ran = new int[] { ranges[0], ranges[1] };
                threads[i] = new Thread(new ParameterizedThreadStart(DownLoad));
                threads[i].Name = System.IO.Path.GetFileNameWithoutExtension(fileName) + "_{0}".Replace("{0}", Convert.ToString(i + 1));
                threads[i].Start(ran);
            }
            watch.Start();
        }
        /// <summary>
        /// 多线程执行的公有方法
        /// </summary>
        private void DownLoad(object obj)
        {
            //下载文件的起始、结束位置
            int[] range = obj as int[];
            string tempFileName = string.Format("{0}.tmp", Thread.CurrentThread.Name);
            tempFiles.Add(tempFileName);
            FileStream fs = File.OpenRead(fileName);
            Console.WriteLine("threads:{2},start:{0},end:{1}", range[0], range[1], Thread.CurrentThread.Name);
            //循环取文件字节流
            /*
                0      100
                101  200
                201  300
            */
            long left = range[1] - range[0];
            byte[] bytes = new byte[left + 1];
            int num = 0;
            int start = range[0];

            using (FileStream writer = new FileStream(tempFileName, FileMode.Create))
            {
                using (BinaryWriter bw = new BinaryWriter(writer))
                {
                    //fs.Position = range[0];
                    //fs.Read(bytes, 0, bytes.Length);
                    //bw.Write(bytes);
                    //bw.Flush();
                    byte[] buffers = new byte[1024 * 512];
                    int maxLength = buffers.Length;
                    while (left + 1 > 0)
                    {
                        fs.Position = start;
                        num = 0;
                        if (left < maxLength)
                        {
                            buffers = new byte[left + 1];
                            num = fs.Read(buffers, 0, buffers.Length);
                        }
                        else
                            num = fs.Read(buffers, 0, maxLength);
                        if (num == 0) break;
                        start += num;
                        left -= num;
                        bw.Write(buffers);
                        bw.Flush();
                    }
                }
            }
            fs.Close();

            Interlocked.Increment(ref completeNum);
            Console.WriteLine("thread:{0}，complete:{1}", Thread.CurrentThread.Name, completeNum);
            //判断所有线程是否都已完成
            if (completeNum == threadNum)
            {
                if (Interlocked.CompareExchange(ref theLock, 1, 0) == 1)
                    Console.WriteLine("denied threads:{0}", Thread.CurrentThread.Name);
                else
                    Complete();
            }
        }
        /// <summary>
        /// 合并文件
        /// </summary>
        private void Complete()
        {
            Stream mergeFile = new FileStream(saveFileName, FileMode.Create);
            BinaryWriter writer = new BinaryWriter(mergeFile);
            tempFiles.Sort((left, right) =>
            {
                int intLeft = left.Split('.')[0].Last();
                int intRight = right.Split('.')[0].Last();
                if (intLeft > intRight)
                    return 1;
                else if (intLeft == intRight)
                    return 0;
                else
                    return -1;
            });
            foreach (var file in tempFiles)
            {
                using (FileStream fs = new FileStream(file, FileMode.Open))
                {
                    BinaryReader reader = new BinaryReader(fs);
                    writer.Write(reader.ReadBytes((int)fs.Length));
                    reader.Close();
                }
                File.Delete(file);
                Console.WriteLine("tempFileName:{0}", file);
            }
            writer.Close();
            Console.WriteLine("game over!!!");
            watch.Stop();
            Console.WriteLine("total running:{0}", watch.Elapsed.Milliseconds);
        }
    }
}
